﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;

namespace VsTortoiseSVN
{
    public class TortoiseProcMissingException : Exception
    {
    }

    /// <summary>
    /// TortoiseProc is a helper class to ease execution of TortoiseSVN commands.
    /// </summary>
    public class TortoiseProc
    {
        enum Revision
        {
            Head = -1,
            Base = -2,
            WorkingCopy = -3,
        }

        /// <summary>
        /// Gets the full path to TortoiseProc.exe
        /// </summary>
        [DefaultValue("TortoiseProc.exe")]
        public string FilePath
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets whether TSVN is available on the computer.
        /// </summary>
        public bool IsAvailable
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets whether TSVN is version 1.5 or newer.
        /// (TSVN /pathfile option is available in 1.5 and newer only)
        /// </summary>
        public bool Is150OrNewer
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets TSVN version information.
        /// </summary>
        public System.Diagnostics.FileVersionInfo VersionInfo
        {
            get;
            private set;
        }

        #region Singleton
        private static TortoiseProc _instance;
        public static TortoiseProc Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new TortoiseProc();

                return _instance;
            }
        }
        #endregion

        private TortoiseProc()
        {
            Output.Instance.WriteLine("Trying to find TortoiseSVN path...");

            string filePath;
            if (FindTSVNPath(out filePath))
                FilePath = filePath;

            IsAvailable = File.Exists(FilePath);
            Output.Instance.WriteLine(string.Format("TortoiseProc.IsAvailable: {0}", IsAvailable));

            if (IsAvailable)
            {
                Output.Instance.WriteLine(string.Format("TortoiseProc.FilePath: {0}", FilePath));
                VersionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(FilePath);
                int version = (VersionInfo.ProductMajorPart << 16) | VersionInfo.ProductMinorPart;
                Is150OrNewer = version >= ((1 << 16) | 5);
                Output.Instance.WriteLine(string.Format("TortoiseProc.Is150OrNewer: {0}", Is150OrNewer));
                Output.Instance.WriteLine(string.Format("TortoiseProc.VersionInfo: {0}", VersionInfo.FileVersion));
            }
        }
     
        private bool FindTSVNPath(out string filePath)
        {
            const string keyPath = @"Software\TortoiseSVN";
            const string valueName = "ProcPath";

            bool success = Win32.RegReadString(out filePath, Win32.HKEY_LOCAL_MACHINE, Win32.KEY_READ, keyPath, valueName);
            if (success && File.Exists(filePath))
                return true;

            if (Win32.IsWow64())
            {
                success = Win32.RegReadString(out filePath, Win32.HKEY_LOCAL_MACHINE, Win32.KEY_WOW64_64KEY | Win32.KEY_READ, keyPath, valueName);
                if (success && File.Exists(filePath))
                    return true;

                success = Win32.RegReadString(out filePath, Win32.HKEY_LOCAL_MACHINE, Win32.KEY_WOW64_32KEY | Win32.KEY_READ, keyPath, valueName);
                if (success && File.Exists(filePath))
                    return true;
            }

            filePath = string.Empty;
            return false;
        }

        public void Properties(string path)
        {
            Exec("/command:properties", path);
        }

        public void Properties(ICollection<string> paths)
        {
            Exec("/command:properties", paths);
        }

        public void Add(string path)
        {
            Exec("/command:add", path);
        }

        public void Add(ICollection<string> paths)
        {
            Exec("/command:add", paths);          
        }

        public void Cleanup(ICollection<string> paths)
        {
            Exec("/command:cleanup", paths);
        }

        public void Commit(string path)
        {
            Exec("/command:commit", path);
        }

        public void Commit(ICollection<string> paths)
        {
            Exec("/command:commit", paths);
        }
        
        public void Diff(string path)
        {
            Exec("/command:diff", path);
        }
        
        public void Diff(string path, int line)
        {
            string args = string.Format("/command:diff /line:{0}", line);
            Exec(args, path);
        }
        
        public void Diff(ICollection<string> paths)
        {
            Exec("/command:diff", paths);
        }
        
        public void DiffWithPreviousVersion(string path)
        {
            string args = string.Format("/command:prevdiff");
            Exec(args, path);
        }

        public void DiffWithPreviousVersion(string path, int line)
        {
            string args = string.Format("/command:prevdiff /line:{0}", line);
            Exec(args, path);
        }

        public void Lock(ICollection<string> paths)
        {
            Exec("/command:lock", paths);
        }

        public void Unlock(ICollection<string> paths)
        {
            Exec("/command:unlock", paths);
        }

        public void CompareWithBase(ICollection<string> paths)
        {
            foreach (string path in paths)
            {
                // http://tortoisesvn.tigris.org/ds/viewMessage.do?dsForumId=4061&dsMessageId=2448492
                Exec("/command:diff", path);
            }
        }

        public void Blame(string path)
        {
            Exec("/command:blame", path);
        }

        public void Blame(string path, int line)
        {
            string args = string.Format("/command:blame /line:{0}", line);
            Exec(args, path);
        }

        public void EditConflicts(string path)
        {
            Exec("/command:conflicteditor", path);
        }

        public void Resolve(string path)
        {
            Exec("/command:resolve", path);
        }

        public void Resolve(ICollection<string> paths)
        {
            Exec("/command:resolve", paths);
        }

        public void Revert(string path)
        {
            Exec("/command:revert", path);
        }

        public void Revert(ICollection<string> paths)
        {
            Exec("/command:revert", paths);
        }

        public void ShowLog(string path)
        {
            Exec("/command:log", path);
        }

        public void Update(string path)
        {
            Exec("/command:update", path);
        }

        public void Update(ICollection<string> paths)
        {
            Exec("/command:update", paths);
        }

        /// <summary>
        /// Opens the TortoiseSVN "Update to Revision" dialog where the user
        /// can pick the revision number.
        /// </summary>
        public void UpdateToRevision(string path)
        {
            Exec("/command:update /rev", path);
        }

        /// <summary>
        /// Opens the TortoiseSVN "Update to Revision" dialog where the user
        /// can pick the revision number.
        /// </summary>
        public void UpdateToRevision(ICollection<string> paths)
        {
            Exec("/command:update /rev", paths);
        }

        /// <summary>
        /// Opens the TortoiseSVN Repository Browser dialog, pointing to the URL of the working copy given in path.
        /// </summary>
        /// <param name="path"></param>
        public void RepoBrowser(string path)
        {
            Exec("/command:repobrowser", path);
        }

        /// <summary>
        /// Execute TortoiseSVN with the given options string.
        /// </summary>
        private void Exec(string options, string path)
        {
            if (!IsAvailable)
                throw new TortoiseProcMissingException();

            if (!string.IsNullOrEmpty(path))
            {
                if (Is150OrNewer)
                    options += string.Format(@" /path:""{0}""", path);
                else
                    options += string.Format(@" /path:""{0}"" /notempfile", path);
            }

            Win32.CreateProcess(FilePath, options);
        }

        /// <summary>
        /// Execute TortoiseSVN with the given options string.
        /// </summary>
        private void Exec(string options, ICollection<string> paths)
        {
            if (!IsAvailable)
                throw new TortoiseProcMissingException();

            if (paths.Count > 0)
            {
                string pathfilePath = CreatePathFile(paths);
                if (Is150OrNewer)
                    options += string.Format(@" /pathfile:""{0}"" /deletepathfile", pathfilePath);
                else
                    options += string.Format(@" /path:""{0}"" /tempfile", pathfilePath);
            }

            Win32.CreateProcess(FilePath, options);
        }


        /// <summary>
        /// A PathFile is an UTF-16 little-endian encoded text file without BOM.
        /// Every line contains one exactly path we want to pass to TortoiseProc.Instance.
        /// Using a PathFile by-passes the need to specify every filepath via command-line,
        /// because Command-line strings are limited to a certain amount of charaters depending on the OS.
        /// </summary>
        private string CreatePathFile(ICollection<string> paths)
        {
            string filePath = System.IO.Path.GetTempFileName();
            List<string> mergedPaths = new List<string>(paths.Count);
            PathHelper.RemoveSubDirectories(mergedPaths, paths);

            // Output what will be in the pathfile to
            // VsTortoise diagnostics pane
            if (Output.Instance.Enabled)
            {
                Output.Instance.WriteLine(filePath);
                for (int n = 0; n < mergedPaths.Count; ++n)
                {
                    string path = mergedPaths[n];
                    Output.Instance.WriteLine("\t" + path);
                }
            }

            // TSVN expects an UTF-16 encoded little-endian file without Byte Order Mark
            Encoding utf16LittleEndianWithoutBOM = new UnicodeEncoding(false, false);
            
            using (StreamWriter stream = new StreamWriter(filePath, false, utf16LittleEndianWithoutBOM))
            {
                // TSVN expects \n line-breaks, the default \r\n is not supported yet
                stream.NewLine = "\n";

                // Write every path to the stream
                for (int n = 0; n < mergedPaths.Count; ++n)
                {
                    string path = mergedPaths[n];
                    stream.WriteLine(path);
                }
            }

            return filePath;
        }
    }
}
